/*=============================================================================*
 *  Copyright 2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *=============================================================================*/
package topics.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import topics.Topic;
import topics.TopicListener;
import topics.TopicSpace;
import topics.expression.TopicExpression;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 * Simple in-memory implementation of the Topic interface
 */
public class TopicImpl
   implements Topic,
              TopicListener
{
   private static final Log  LOG          = LogFactory.getLog( TopicImpl.class.getName(  ) );

   /** DOCUMENT_ME */
   protected Map             m_subTopics;

   /** DOCUMENT_ME */
   protected TopicExpression m_reference;

   /** DOCUMENT_ME */
   protected Collection m_listeners;

   /** DOCUMENT_ME */
   protected String     m_name;

   /** DOCUMENT_ME */
   protected Object     m_currentMsg;

   /** DOCUMENT_ME */
   protected List       m_topicPath; // ordered set of QNames

   /** DOCUMENT_ME */
   protected TopicSpace m_topicSpace;
   private Topic        m_parent;

   /** DOCUMENT_ME */
   protected boolean m_isVisible = true;

   /**
    * Create a topic with the given name
    *
    * @param name The name of the created topic
    */
   public TopicImpl( String name )
   {
      this( new HashMap(  ), null, new Vector(  ), name, null, null );
   }

   /**
    * Create a topic with the given parameters
    *
    * @param subTopics A map of child topics
    * @param reference A topic expression (only used if this is a topic alias)
    * @param listeners A collection of topic listeners
    * @param name      The name of this topic
    * @param current   The current value of this topic
    * @param topicPath The concrete topic path of this topic
    */
   public TopicImpl( Map             subTopics,
                     TopicExpression reference,
                     Collection      listeners,
                     String          name,
                     Object          current,
                     List            topicPath )
   {
      this.m_subTopics     = subTopics;
      this.m_reference     = reference;
      this.m_listeners     = listeners;
      this.m_name          = name;
      this.m_currentMsg    = current;
      if ( topicPath == null )
      {
         this.m_topicPath = new java.util.LinkedList(  );
         m_topicPath.add( name );
      }
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public Object getCurrentMessage(  )
   {
      return m_currentMsg;
   }

   /**
    * DOCUMENT_ME
    *
    * @param visible DOCUMENT_ME
    */
   public void setIsVisible( boolean visible )
   {
      m_isVisible = visible;
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public String getName(  )
   {
      return m_name;
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public Topic getParent(  )
   {
      return m_parent;
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public boolean isReference(  )
   {
      return m_reference != null;
   }

   /**
    * DOCUMENT_ME
    *
    * @param topicName DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public Topic getTopic( String topicName )
   {
      return (Topic) m_subTopics.get( topicName );
   }

   /**
    * DOCUMENT_ME
    *
    * @param topicPath DOCUMENT_ME
    */
   public void setTopicPath( List topicPath )
   {
      m_topicPath = topicPath;
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public List getTopicPath(  )
   {
      return m_topicPath;
   }

   /**
    * DOCUMENT_ME
    *
    * @param topicPath DOCUMENT_ME
    */
   public void setTopicReference( TopicExpression topicPath )
   {
      this.m_reference = topicPath;
   }

   /**
    * Get the topic expression for the topic(s) that this topic references.
    *
    * @return The topic expression that this topic reference or null if this topic is not a reference.
    */
   public TopicExpression getTopicReference(  )
   {
      return m_reference;
   }

   /**
    * DOCUMENT_ME
    *
    * @param topicSpace DOCUMENT_ME
    */
   public void setTopicSpace( TopicSpace topicSpace )
   {
      m_topicSpace = topicSpace;
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public TopicSpace getTopicSpace(  )
   {
      return m_topicSpace;
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public boolean isVisible(  )
   {
      return m_isVisible;
   }

   /**
    * DOCUMENT_ME
    *
    * @param topic DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public Topic addTopic( Topic topic )
   {
      if ( m_reference != null )
      {
         throw new IllegalStateException( "Cannot modify the TopicExpression reference by adding an additional Topic." );
      }

      m_subTopics.put( topic.getName(  ),
                       topic );

      List topicPath = new LinkedList(  );

      topicPath.addAll( m_topicPath );
      topicPath.add( topic.getName(  ) );

      topic.setTopicPath( topicPath );
      topic.addTopicListener( this );

      topic.setTopicSpace( m_topicSpace );
      ( (TopicImpl) topic ).setParent( this );

      return topic;
   }

   /**
    * DOCUMENT_ME
    *
    * @param name DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public Topic addTopic( String name )
   {
      return addTopic( new TopicImpl( name ) );
   }

   /**
    * DOCUMENT_ME
    *
    * @param listener DOCUMENT_ME
    */
   public synchronized void addTopicListener( TopicListener listener )
   {
      m_listeners.add( listener );
   }

   /**
    * DOCUMENT_ME
    *
    * @param name DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public boolean containsTopic( String name )
   {
      return m_subTopics.containsKey( name );
   }

   /**
    * DOCUMENT_ME
    *
    * @param msg DOCUMENT_ME
    *
    * @throws Exception DOCUMENT_ME
    */
   public void publish( Object msg )
   throws Exception
   {
      m_currentMsg = msg;
      if ( LOG.isDebugEnabled(  ) )
      {
         LOG.debug( "Publish called on topic " + m_name + " with message " + msg );
      }

      this.topicChanged( this );
   }

   /**
    * DOCUMENT_ME
    *
    * @param name DOCUMENT_ME
    */
   public void removeTopic( String name )
   {
      m_subTopics.remove( name );
   }

   /**
    * DOCUMENT_ME
    *
    * @param topic DOCUMENT_ME
    */
   public void removeTopic( Topic topic )
   {
      m_subTopics.remove( topic.getName(  ) );
      topic.removeTopicListener( this );
   }

   /**
    * DOCUMENT_ME
    *
    * @param listener DOCUMENT_ME
    */
   public synchronized void removeTopicListener( TopicListener listener )
   {
      this.m_listeners.remove( listener );
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public String toString(  )
   {
      // TODO: *SJC* make this print the full concrete path of this Topic instead of just its name
      return getName(  );
   }

   /**
    * DOCUMENT_ME
    *
    * @param topic DOCUMENT_ME
    */
   public synchronized void topicChanged( Topic topic )
   {
      Iterator      listenerIterator = this.m_listeners.iterator(  );
      TopicListener listener;
      while ( listenerIterator.hasNext(  ) )
      {
         listener = (TopicListener) listenerIterator.next(  );
         listener.topicChanged( topic );
      }
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public Iterator topicIterator(  )
   {
      return m_subTopics.values(  ).iterator(  );
   }

   /**
    * DOCUMENT_ME
    *
    * @return DOCUMENT_ME
    */
   public synchronized Iterator topicListenerIterator(  )
   {
      return this.m_listeners.iterator(  );
   }

   /**
    * DOCUMENT_ME
    *
    * @param parent DOCUMENT_ME
    */
   void setParent( Topic parent )
   {
      m_parent = parent;
   }
}